package de.op.projectc.objects;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.BodyDef;
import com.badlogic.gdx.physics.box2d.ChainShape;
import com.badlogic.gdx.physics.box2d.FixtureDef;
import com.badlogic.gdx.scenes.scene2d.Actor;
import de.op.projectc.helper.CameraHelper;
import de.op.projectc.helper.WorldHelper;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

/**
 * Created by Andy on 17.04.14.
 */
public class TerrainWorld {
    private Texture texture;
    private ShaderProgram shader;

    private Body groundBody;

    private final Heli heli;
    private final float DIFFICULTY = 5f;
    private final int VERTIEX_DISTANCE = 1;
    private Deque<Terrain> activeLeftTerrains;
    private Deque<Terrain> activeRightTerrains;

    public TerrainWorld(Heli heli){
        this.heli = heli;

        BodyDef groundBodyDef = new BodyDef();
        groundBodyDef.type = BodyDef.BodyType.StaticBody;
        groundBodyDef.position.set(new Vector2(0, 0));

        groundBody = WorldHelper.getInstance().createBody(groundBodyDef);

        ChainShape groundBox = new ChainShape();
        groundBox.createChain(new Vector2[]{new Vector2(0, 0),
               new Vector2(CameraHelper.getInstance().viewportWidth, 0)});

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = groundBox;
        fixtureDef.filter.groupIndex = -1;

        groundBody.createFixture(fixtureDef);

        groundBox.dispose();

        texture = new Texture(Gdx.files.internal("dirt.jpg"));
        texture.setWrap(Texture.TextureWrap.Repeat, Texture.TextureWrap.Repeat);

        createShader();
        activeLeftTerrains = new ArrayDeque<Terrain>();
        activeRightTerrains = new ArrayDeque<Terrain>();

        generateStartTerrain();
    }

    private void createShader() {
        String vertexShader = "attribute vec4 a_position;    \n"
                + "attribute vec4 a_color;       \n"
                + "attribute vec2 a_texCoords;   \n"
                + "uniform mat4 u_worldView;\n"
                + "varying vec4 v_color;         \n"
                + "varying vec2 v_texCoords;     \n"
                + "\n"
                + "void main()                   \n"
                + "{                             \n"
                + "   v_color = vec4(1,1,1,1);//a_color;         \n"
                + "   v_texCoords = a_texCoords; \n"
                + "   gl_Position = u_worldView * a_position;  \n"
                + "}                             \n";

        // this one tells it what goes in between the points (i.e colour/texture)
        String fragmentShader = "#ifdef GL_ES                \n"
                + "precision mediump float;    \n"
                + "#endif                      \n"
                + "varying vec4 v_color;       \n"
                + "varying vec2 v_texCoords;   \n"
                + "uniform sampler2D u_texture;\n"
                + "void main()                 \n"
                + "{                           \n"
                + "  gl_FragColor = v_color * texture2D(u_texture, v_texCoords);   \n"
                + "}                           \n";

        //http://meatcat.info/wordpress/?p=35
        shader = new ShaderProgram(vertexShader, fragmentShader);

        if (shader.isCompiled() == false) {
            Gdx.app.log("ShaderTest", shader.getLog());
            Gdx.app.exit();
        }
    }

    void generateStartTerrain() {
        int width = (int) CameraHelper.getInstance().viewportWidth;
        int mid = width / 2;
        int height = (int) CameraHelper.getInstance().viewportHeight;
        int startPosition = 0;//height;

        float freeSpace = this.heli.getWidth() * 2;

        ArrayList<Vector2> leftVertices = new ArrayList<Vector2>();
        ArrayList<Vector2> rightVertices = new ArrayList<Vector2>();
        leftVertices.add(new Vector2(0, startPosition));
        rightVertices.add(new Vector2(width, startPosition));

        for(int i = 0 ; i <= height * DIFFICULTY; i += VERTIEX_DISTANCE) {

            //double position = Math.sin((2*Math.PI * ((float) i/height)) / DIFFICULTY) * mid;
            //double position = Math.sin((float) i * DIFFICULTY) * mid;
            double s = 2 * Math.PI * ((float) i/(height * DIFFICULTY));
            double position = Math.sin(s) * mid;

            position += mid;

            // Gdx.app.log("skyline[i]:", i + " - " + position);
           float left = (float) Math.min(width, Math.max(0, position - freeSpace));
           float right = (float) Math.min(width, Math.max(0, position + freeSpace));

           leftVertices.add(new Vector2(left, (float) startPosition + i));
           leftVertices.add(new Vector2(0, (float) startPosition + i));

            rightVertices.add(new Vector2(right, (float) startPosition + i));
            rightVertices.add(new Vector2(width, (float) startPosition + i));
        }
        //leftVertices.add(new Vector2(0, startPosition + height * DIFFICULTY));
        //rightVertices.add(new Vector2(width, startPosition + height * DIFFICULTY));

        this.activeLeftTerrains.add(new Terrain(leftVertices));
        this.activeRightTerrains.add(new Terrain(rightVertices));

        createStaticBody(leftVertices, 1);
        createStaticBody(rightVertices, 1);
    }

    void generateNextTerrain() {
        int width = (int) CameraHelper.getInstance().viewportWidth;
        int mid = width / 2;
        int height = (int) CameraHelper.getInstance().viewportHeight;

        float freeSpace = this.heli.getWidth() * 2;

        ArrayList<Vector2> leftVertices = new ArrayList<Vector2>();
        ArrayList<Vector2> rightVertices = new ArrayList<Vector2>();

        //kleine Ueberlappung, damit kein Flimmern auftaucht
        leftVertices.addAll(this.activeLeftTerrains.getLast().getLastVertices(3));
        rightVertices.addAll(this.activeRightTerrains.getLast().getLastVertices(3));

        int startPosition = (int) this.activeLeftTerrains.getLast().getLastVertex().y;

        for(int i = 0; i <= height * DIFFICULTY; i += VERTIEX_DISTANCE) {

            double s = 2 * Math.PI * ((float) i/(height * DIFFICULTY));
            double position = Math.sin(s) * mid;

            position += mid;

            //Gdx.app.log("next[i]:", 2 * Math.PI * ((float) i/height) + " - " + Math.sin(s * DIFFICULTY) * mid);

            float left = (float) Math.min(width, Math.max(0, position - freeSpace));
            float right = (float) Math.min(width, Math.max(0, position + freeSpace));

            leftVertices.add(new Vector2(left, (float) startPosition + i));
            leftVertices.add(new Vector2(0, (float) startPosition + i));

            rightVertices.add(new Vector2(right, (float) startPosition + i));
            rightVertices.add(new Vector2(width, (float) startPosition + i));
        }
        //leftVertices.add(new Vector2(0, startPosition + height * DIFFICULTY));
        //rightVertices.add(new Vector2(width, startPosition + height * DIFFICULTY));

        this.activeLeftTerrains.add(new Terrain(leftVertices));
        this.activeRightTerrains.add(new Terrain(rightVertices));

        createStaticBody(leftVertices, 3);
        createStaticBody(rightVertices, 3);
    }

    private void createStaticBody(List<Vector2> verticeList, int offset) {
        Vector2[] vertices = new Vector2[(verticeList.size() - offset) / 2];
        for(int i = offset, j = 0; i < verticeList.size(); i += 2, j++) {
            vertices[j] = verticeList.get(i);
        }
        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyDef.BodyType.StaticBody;

        Body body = WorldHelper.getInstance().createBody(bodyDef);

        ChainShape shape = new ChainShape();
        shape.createChain(vertices);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = shape;
        fixtureDef.friction = 0f;
        fixtureDef.restitution = 0f; // bounce
        fixtureDef.filter.groupIndex = 2;

        body.createFixture(fixtureDef);

        shape.dispose();
    }

    public void update(float delta) {
        float highestPoint = this.activeLeftTerrains.getLast().getLastVertex().y;

        if(this.heli.getPositionY() > highestPoint - CameraHelper.getInstance().viewportHeight * 0.55f) {
            this.generateNextTerrain();
        }
    }

    public void render(SpriteBatch spriteBatch) {
        GL20 gl = Gdx.graphics.getGL20();

        gl.glEnable(GL20.GL_TEXTURE_2D);
        gl.glActiveTexture(GL20.GL_TEXTURE0);

        shader.begin();
        //this sets our uniform 'u_texture' (i.e. the gl texture we want to use) to 0.
        shader.setUniformMatrix("u_worldView", CameraHelper.getInstance().combined);
        shader.setUniformi("u_texture", 0);
        texture.bind();

        //mesh.render(shader, GL20.GL_TRIANGLES);
        //testMesh.render(shader, GL20.GL_TRIANGLE_FAN);

        for(Terrain terrain : activeLeftTerrains) {
            terrain.getTerrainMesh().render(shader, GL20.GL_TRIANGLE_STRIP);
        }

        for(Terrain terrain : activeRightTerrains) {
            terrain.getTerrainMesh().render(shader, GL20.GL_TRIANGLE_STRIP);
        }
        shader.end();
    }

    public void dispose() {
        this.texture.dispose();
        this.shader.dispose();

        for(Terrain terrain : activeLeftTerrains) {
            terrain.dispose();
        }
        for(Terrain terrain : activeRightTerrains) {
            terrain.dispose();
        }
    }
}